/**
 *  @file HttpRequest.cpp
 */
#include "HttpRequest.h"
#include "../../common/CppMemPool.h"
#include "../../convert/Convert.h"
#include "util/HttpUtility.h"

using namespace cppflib::collections;

namespace cppflib
{

namespace net
{

namespace http
{

/**
 *  Prepend protocol to URL
 */
PRIVATE void __PrependProtocolScheme(CFString &url)
{
   if (!url.StartsWith(PROTO_HTTP) && !url.StartsWith(PROTO_HTTPS))
      url.Insert(0, PROTO_HTTP);
}

/**
 *  @return > 0 -- URL starts with either http:// or https://
 *  @return 0 -- not starts with a protocol scheme
 */
PRIVATE int __GetUrlStart(const CFString &url)
{
   int start = 0;
   if (url.StartsWith(PROTO_HTTPS)) {
      start = static_cast<int>(CFString::WStrlen(PROTO_HTTPS));
   }
   else if (url.StartsWith(PROTO_HTTP)) {
      start = static_cast<int>(CFString::WStrlen(PROTO_HTTP));
   }
   
   return start;  
   
}

/**
 *  Extract and remove HTTP port number embedded in URL
 * 
 *  e.g. "http://www.abc.com:8080/"  --> 8080
 *       "https://www.abc.com/"  --> 443 (not specified, return https default)  
 *
 *  @return port number extracted
 */
PRIVATE int __ExtractAndRemovePortFromUrl(CFString &url)
{
   int start = 0;
   if ((start = __GetUrlStart(url)) == 0)
      return 0;
   
   int end = -1; 
   do {
      if ((end = url.Find(_S('/'), start)) >= 0) break;
      if ((end = url.Find(_S('?'), start)) >= 0) break;
      end = url.GetLength();
   } while(false);
   
   start = url.Find(_S(':'), start);
   if (start < 0 || start > end) // custom port not found, use default
      return (url.StartsWith(PROTO_HTTPS)) ? HTTPS_PORT : HTTP_PORT;

   CFString portStr = url.Mid(start + 1, end - (start + 1));
   int ret = convert::ToInt32(portStr);
   
   url.Delete(start, end - start); // remove the custom port
   return ret;
}


HttpRequest::HttpRequest(void)
{
}

HttpRequest::HttpRequest(httpmethod_t method)
{
   this->method = method;
   this->portNum = 0;
}

HttpRequest::~HttpRequest(void)
{
}

/**
 *  Set a simple request URL. A "http://" will be prepended if not specified
 *
 *  By default, http will use 80 port and https will use 443.
 *  Specify a custom port in the URL like:
 *           http://www.hello.com:8080/index.html
 *           https://www.securehello.com:1234/index.html
 *  
 */
void HttpRequest::SetUrl(pcwstr_t plainUrl)
{
   fullUrl = plainUrl;
   __PrependProtocolScheme(fullUrl);
   this->portNum = __ExtractAndRemovePortFromUrl(fullUrl);
}

/**
 *  This method is going to construct a URL like
 *      http:://www.hello.com/page?x1=y1&x2=y2&x3=y3  
 *
 *  Also, any illegal characters in the key-value parameter list will be escaped.
 */
void HttpRequest::SetUrl(pcwstr_t urlPath, const NameValueList &keyValueParameters)
{
   fullUrl = urlPath;
   if (keyValueParameters.GetCount() > 0) {
      fullUrl += _S("?");
      util::UrlEncodeNameValuePairs(keyValueParameters, fullUrl);
   }

   __PrependProtocolScheme(fullUrl);
   this->portNum = __ExtractAndRemovePortFromUrl(fullUrl);
}


/**
 *  Get method of this request
 */
httpmethod_t HttpRequest::GetMethod()
{
   return this->method;
}

/**
 *  Get the resultant complete URL
 */
void HttpRequest::GetURL(CFString &url)
{
   url = this->fullUrl;
}

/**
 *  Get URL when accessing through proxy, which is a full URL with port number embedded (if not 80)
 *  e.g.
 *        http://www.xyz.com:8080/path/to/request
 */
void HttpRequest::GetURLForProxy(CFString &url)
{
   GetURL(url);

   if (GetPort() != http::HTTP_PORT) {

      CFString portStr;
      portStr.Format(_S(":%d"), GetPort());
   
      int start = __GetUrlStart(url);

      int end = -1;
      if ((end = url.Find(_S('/'), start)) >= 0) {
         url.Insert(end, portStr);  // insert port number after host
      }
      else if ((end = url.Find(_S('?'), start)) >= 0) {
         portStr += _S('/');
         url.Insert(end, portStr);  // insert port number after host
      }
      else {
         url += portStr;  // just append at the end
         url += _S('/');
      }
   }
}

/**
 *  Extract the host part from url
 */
void HttpRequest::GetHost(CFString &host)
{
   host = _S("");

   int start = 0;
   if ((start = __GetUrlStart(fullUrl)) == 0)
      return;

   int end = -1; 
   do {
      if ((end = fullUrl.Find(_S('/'), start)) >= 0) break;
      if ((end = fullUrl.Find(_S('?'), start)) >= 0) break;
      end = fullUrl.GetLength();
   } while(false);

   host = fullUrl.Mid(start, end - start);
}

/**
 *  Get request path, i.e. the path without protocol and host name
 *
 *  @param [in] includeUrlParams -- true to include URL parameters appended at the end
 */
void HttpRequest::GetReqPath(CFString &path, bool includeUrlParams)
{
   path = _S("");

   int start = 0;
   if ((start = __GetUrlStart(fullUrl)) == 0)
      return;

   int end = fullUrl.Find(_S('/'), start);
   if (end < 0) {
      path = _S("/");  // assume to get root 
   }
   else {
      start = end;
      end = includeUrlParams ? -1 : fullUrl.Find(_S('?'), start);  // any appended parameters ?
      path = (end < 0) ? fullUrl.Mid(start) : fullUrl.Mid(start, end - start);
   }
}

/**
 *  @return target port number 
 */
int HttpRequest::GetPort()
{
   return this->portNum;
}

/**
 *  This request use SSL or not
 */
bool HttpRequest::IsSSLRequired()
{
   return fullUrl.StartsWith(PROTO_HTTPS);
}

/**
 *  Check the target host and port of this request against another request
 */
bool HttpRequest::IsSameHostAndPort(HttpRequest &req)
{
   CFString curHost, anotherHost;

   this->GetHost(curHost);
   req.GetHost(anotherHost);

   if (curHost.CompareNoCase(anotherHost) != 0)   // check host (case insensitive)
      return false;

   if (this->GetPort() != req.GetPort())  // check port
      return false;

   if (this->IsSSLRequired() != req.IsSSLRequired()) // check protocol scheme
      return false;

   return true;
}

/**
 *  @return index just beyond the '?' mark
 *  @return -1 if not found
 */
PRIVATE int __GetUrlParamStart(const CFString &url)
{
   do {
      int start = 0;
      if ((start = __GetUrlStart(url)) == 0)
         break;

      int end = url.Find(_S('?'), start);
      if (end < 0) 
         break;  // no '?'
      
      if (end + 1 >= url.GetLength()) // no characters after '?'
         break;

      return end + 1;

   } while (false);

   return -1;
}

/**
 *  @return true if there are parameters appended at the end of request URL
 */
bool HttpRequest::HasUrlParams(void)
{
   return (__GetUrlParamStart(fullUrl) >= 0);
}

/**
 *  Get the URL parameters after '?' in the query string
 */
bool HttpRequest::GetUrlParams(NameValueList &keyValueParameters)
{
   int start = __GetUrlParamStart(fullUrl);
   if (start < 0)
      return false;

   CFString nameValStr = fullUrl.Mid(start);
   util::DecodeUrlEncodedNameValueString(nameValStr, keyValueParameters);
   return true;
}


} // end of namespace http

} // end of namespace net

} // end of namespace cppflib

